//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

#include <stdio.h>

#define OPENMAYA_EXPORT

#include <maya/MIOStream.h>
#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MStringArray.h>
#include <maya/MConditionMessage.h>
#include <maya/MFnPlugin.h>

#include <maya/MPxCommand.h>
#include <maya/MSyntax.h>
#include <maya/MArgDatabase.h>

///////////////////////////////////////////////////
//
// Definitions
//
///////////////////////////////////////////////////

// Message flag
//
#define kMessageFlag		"m"
#define kMessageFlagLong	"message"

///////////////////////////////////////////////////
//
// Declarations
//
///////////////////////////////////////////////////

// Callback function for messages
//
static void conditionChangedCB(bool state, void * data);

// Array of callback ids.
//
typedef MCallbackId* MCallbackIdPtr;
static MCallbackIdPtr callbackId = NULL;

// Array of condition names.
//
static MStringArray conditionNames;

///////////////////////////////////////////////////
//
// Command class declaration
//
///////////////////////////////////////////////////
class conditionTest : public MPxCommand
{
public:
	conditionTest();
	virtual                 ~conditionTest(); 

	MStatus                 doIt( const MArgList& args );

	static MSyntax			newSyntax();
	static void*			creator();

private:
	MStatus                 parseArgs( const MArgList& args );

	bool					addMessage;
	bool					delMessage;

	MStringArray			conditions;
};


///////////////////////////////////////////////////
//
// Command class implementation
//
///////////////////////////////////////////////////

// Constructor
//
conditionTest::conditionTest()
:	addMessage(false)
,	delMessage(false)
{
	conditions.clear();
}

// Destructor
//
conditionTest::~conditionTest()
{
	// Do nothing
}

// creator
//
void* conditionTest::creator()
{
	return (void *) (new conditionTest);
}

// newSyntax
//
MSyntax conditionTest::newSyntax()
{
	MSyntax syntax;

	syntax.addFlag(kMessageFlag, kMessageFlagLong, MSyntax::kBoolean);
	syntax.setObjectType(MSyntax::kStringObjects);

	return syntax;
}

// parseArgs
//
MStatus conditionTest::parseArgs(const MArgList& args)
{
	MStatus			status;
	MArgDatabase	argData(syntax(), args);

	if (argData.isFlagSet(kMessageFlag))
	{
		bool flag;

		status = argData.getFlagArgument(kMessageFlag, 0, flag);
		if (!status)
		{
			status.perror("could not parse message flag");
			return status;
		}

		if (flag)
		{
			addMessage = true;
		}
		else
		{
			delMessage = true;
		}
	}

	status = argData.getObjects(conditions);
	if (!status)
	{
		status.perror("could not parse condition names");
	}

	// If there are no conditions specified, operate on all of them
	//
	if (conditions.length() == 0)
	{
		// conditionNames is set in initializePlugin to all the
		// currently available condition names.
		//
		conditions = conditionNames;
	}

	return status;
}

// doIt
//
MStatus conditionTest::doIt(const MArgList& args)
{
	MStatus status;

	status = parseArgs(args);
	if (!status)
	{
		return status;
	}

	// Allocate an array of indices.  conditions[n] is a user provided
	// condition name.  Look it up in the static conditionNames array
	// and set indices[n] to the index of the entry in conditionNames.
	//
	// This maps the user specified conditions to the global conditions
	// so we can track callback adds and removes globally.
	//
	int * indices = new int [conditions.length()];

	int i, j;

	for (i = 0; i < (int)conditions.length(); ++i)
	{
		// Initialize the entry to "not found".
		//
		indices[i] = -1;

		// Search condition names for a match.
		//
		for (j = 0; j < (int)conditionNames.length(); ++j)
		{
			if (conditions[i] == conditionNames[j])
			{
				// Found a match.  Store the index and stop looking for
				//
				indices[i] = j;
				break;
			}
		}
	}

	for (i = 0; i < (int)conditions.length(); ++i)
	{
		j = indices[i];
		if (j == -1)
		{
			MGlobal::displayWarning(conditions[i] +
									MString("is not a valid condition name\n"));
			break;
		}

		if (addMessage && callbackId[j] == 0)
		{
			callbackId[j] = MConditionMessage::addConditionCallback(
				conditions[i],
				conditionChangedCB,
				(void*)(size_t)j,
				&status);
			
			if (!status)
			{
				status.perror("failed to add callback for " + conditions[i]);
				callbackId[j] = 0;
			}
		}
		else if (delMessage && callbackId[j] != 0)
		{
			status = MMessage::removeCallback(callbackId[j]);

			if (!status)
			{
				status.perror("failed to remove callback for " + conditions[i]);
			}

			callbackId[j] = 0;
		}
	}

	// Ok, we've made all the necessary changes.  Now show the status.
	//

	MGlobal::displayInfo("Condition Name        State  Msgs On\n");
	MGlobal::displayInfo("--------------------  -----  -------\n");

	char tmpStr[128];
	bool state, msgs;
	
	for (i = 0; i < (int)conditions.length(); ++i)
	{
		j = indices[i];
		if (j == -1)
		{
			continue;
		}

		state = MConditionMessage::getConditionState(conditions[i], &status);
		if (!status)
		{
			status.perror("failed to get status for " + conditions[i]);
			state = false;
		}

		msgs = (callbackId[j] != 0);

		sprintf(tmpStr, "%-20s  %-5s  %s\n",
				conditions[i].asChar(),
				state ? "true" : "false",
				msgs  ? "yes"  : "no");

		MGlobal::displayInfo(tmpStr);
	}

	// Free up the indices we allocated.
	//
	delete [] indices;

	return status;
}

///////////////////////////////////////////////////
//
// Plug-in functions
//
///////////////////////////////////////////////////

MStatus initializePlugin( MObject obj )
{ 
	MStatus   status;
	MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");

	status = MConditionMessage::getConditionNames(conditionNames);
	if (!status)
	{
		return status;
	}

	cout<<"conditionTest: "<<conditionNames.length()<<" conditions are defined."<<endl;

	callbackId = new MCallbackId [conditionNames.length()];

	if (!callbackId)
	{
		return MStatus(MS::kInsufficientMemory);
	}

	for (unsigned int i = 0; i < conditionNames.length(); ++i)
	{
		callbackId[i] = 0;
	}

	// Register the command so we can actually do some work
	//
	status = plugin.registerCommand("conditionTest",
									conditionTest::creator,
									conditionTest::newSyntax);

	if (!status)
	{
		status.perror("registerCommand");
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus   status;
	MFnPlugin plugin( obj );

	// Loop through all the ids and remove the callbacks
	//
	int i;
	int len = conditionNames.length();

	for (i = 0; i < len; ++i)
	{
		if (callbackId[i] != 0)
		{
			MGlobal::displayWarning("Removing callback for " +
									conditionNames[i] +
									"\n");
			MMessage::removeCallback(callbackId[i]);
			callbackId[i] = 0;
		}
	}

	conditionNames.clear();

	delete [] callbackId;

	// Deregister the command
	//
	status = plugin.deregisterCommand("conditionTest");

	if (!status)
	{
		status.perror("deregisterCommand");
	}

	return status;
}

///////////////////////////////////////////////////
//
// Callback function
//
///////////////////////////////////////////////////

static void conditionChangedCB(bool state, void * data)
{
	int i = (int)(size_t)data;

	if (i > 0 && i < (MIntPtrSz)conditionNames.length())
	{
		MGlobal::displayInfo("condition " +
							 conditionNames[i] +
							 " changed to " +
							 (state ? "true\n" : "false\n"));
	}
	else
	{
		MGlobal::displayWarning("BOGUS client data in conditionChangedCB!\n");
	}
}
